import base64
import json
import os

import requests
from alibabacloud_dingtalk.oauth2_1_0 import models as dingtalkoauth_2__1__0_models
from alibabacloud_dingtalk.oauth2_1_0.client import Client as dingtalkoauth2_1_0Client
from alibabacloud_dingtalk.robot_1_0 import models as dingtalkrobot__1__0_models
from alibabacloud_dingtalk.robot_1_0.client import Client as dingtalkrobot_1_0Client
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_tea_util import models as util_models

from common import DingtalkUrl, constants
from common.LoggerFactory import Log
from common.util import Util
from config import configuration
from service_file import RedisUtils


class DingTalkClient:

    @staticmethod
    def create_client2_1() -> dingtalkoauth2_1_0Client:
        config = open_api_models.Config()
        config.protocol = 'https'
        config.region_id = 'central'
        return dingtalkoauth2_1_0Client(config)

    @staticmethod
    def create_client1_0() -> dingtalkrobot_1_0Client:
        config = open_api_models.Config()
        config.protocol = 'https'
        config.region_id = 'central'
        return dingtalkrobot_1_0Client(config)


class DingTalkSDK:
    # 文本消息
    TEXT_TYPE = "sampleText"
    # 图片消息
    IMAGE_TYPE = "sampleImageMsg"
    # Markdown消息
    MARKDOWN_TYPE = "sampleMarkdown"
    # 文件消息
    FILE_TYPE = "sampleFile"
    # 成功的状态码
    success_status_code = 200
    # 失败的消息键
    err_code_key = "errcode"
    err_msg_key = "errmsg"

    def __init__(self):
        self.__client2_1 = DingTalkClient.create_client2_1()
        self.__client1_0 = DingTalkClient.create_client1_0()

    def send_text_msg(self, context, access_token, msg):
        """
        发送文本消息
        :param context: 上下文信息，携带的配置信息
        :param access_token: 接口调用凭证
        :param msg: 消息内容
        """
        robot_code = context.get(configuration.DING_ROBOT_CODE_ATT)
        open_conversation_id = context.get(configuration.DING_OPEN_CONVERSATION_ID_ATT)
        param = {"content": msg}
        org_group_send_headers = dingtalkrobot__1__0_models.OrgGroupSendHeaders()
        org_group_send_headers.x_acs_dingtalk_access_token = access_token
        org_group_send_request = dingtalkrobot__1__0_models.OrgGroupSendRequest(
            msg_param=f'{param}',
            msg_key=self.TEXT_TYPE,
            robot_code=robot_code,
            open_conversation_id=open_conversation_id
        )
        resp = self.__client1_0.org_group_send_with_options(org_group_send_request, org_group_send_headers,
                                                            util_models.RuntimeOptions())
        if resp.status_code != self.success_status_code:
            Log.file().error(f"文本消息发送失败！MSG：{msg}")

    def send_markdown_msg(self, context, access_token, title, markdown_text):
        """
        发送Markdown消息
        :param context: 上下文信息，携带的配置信息
        :param title: 标题
        :param access_token: 接口调用凭证
        :param markdown_text: Markdown消息文本
        """
        robot_code = context.get(configuration.DING_ROBOT_CODE_ATT)
        open_conversation_id = context.get(configuration.DING_OPEN_CONVERSATION_ID_ATT)
        param = {"title": title, "text": markdown_text}
        org_group_send_headers = dingtalkrobot__1__0_models.OrgGroupSendHeaders()
        org_group_send_headers.x_acs_dingtalk_access_token = access_token
        org_group_send_request = dingtalkrobot__1__0_models.OrgGroupSendRequest(
            msg_param=f'{param}',
            msg_key=self.MARKDOWN_TYPE,
            robot_code=robot_code,
            open_conversation_id=open_conversation_id
        )
        resp = self.__client1_0.org_group_send_with_options(org_group_send_request, org_group_send_headers,
                                                            util_models.RuntimeOptions())
        if resp.status_code != self.success_status_code:
            Log.file().error(f"Markdown消息发送失败！标题：{title}")

    def send_image_msg(self, context, access_token, url):
        """
        发送图片消息（目前存在无法显示图像的BUG，改用{@link #send_markdown_image_msg}）
        :param context: 上下文信息，携带的配置信息
        :param access_token: 接口调用凭证
        :param url: 图片的访问地址
        """
        robot_code = context.get(configuration.DING_ROBOT_CODE_ATT)
        open_conversation_id = context.get(configuration.DING_OPEN_CONVERSATION_ID_ATT)
        param = {"photoURL": url}
        org_group_send_headers = dingtalkrobot__1__0_models.OrgGroupSendHeaders()
        org_group_send_headers.x_acs_dingtalk_access_token = access_token
        org_group_send_request = dingtalkrobot__1__0_models.OrgGroupSendRequest(
            msg_param=f'{param}',
            msg_key=self.IMAGE_TYPE,
            robot_code=robot_code,
            open_conversation_id=open_conversation_id
        )
        resp = self.__client1_0.org_group_send_with_options(org_group_send_request, org_group_send_headers,
                                                            util_models.RuntimeOptions())
        if resp.status_code != self.success_status_code:
            Log.file().error(f"图片消息发送失败！URL：{url}")

    def send_markdown_image_msg(self, context, access_token, title, url):
        """
        发送图片消息
        :param context: 上下文信息，携带的配置信息
        :param title: 标题
        :param access_token: 接口调用凭证
        :param url: 图片的访问地址
        """
        robot_code = context.get(configuration.DING_ROBOT_CODE_ATT)
        open_conversation_id = context.get(configuration.DING_OPEN_CONVERSATION_ID_ATT)
        param = {"title": title, "text": f"![]({url})"}
        org_group_send_headers = dingtalkrobot__1__0_models.OrgGroupSendHeaders()
        org_group_send_headers.x_acs_dingtalk_access_token = access_token
        org_group_send_request = dingtalkrobot__1__0_models.OrgGroupSendRequest(
            msg_param=f'{param}',
            msg_key=self.MARKDOWN_TYPE,
            robot_code=robot_code,
            open_conversation_id=open_conversation_id
        )
        resp = self.__client1_0.org_group_send_with_options(org_group_send_request, org_group_send_headers,
                                                            util_models.RuntimeOptions())
        if resp.status_code != self.success_status_code:
            Log.file().error(f"图像文本发送失败！标题：{title}")

    def send_file_msg(self, context, access_token, media_id, file_name, file_suffix):
        """
        发送文件消息
        :param context: 上下文信息，携带的配置信息
        :param access_token: 接口调用凭证
        :param media_id: 媒体文件上传后获取的唯一标识
        :param file_name: 文件名称
        :param file_suffix: 文件后缀类型
        """
        robot_code = context.get(configuration.DING_ROBOT_CODE_ATT)
        open_conversation_id = context.get(configuration.DING_OPEN_CONVERSATION_ID_ATT)
        param = {"mediaId": media_id, "fileName": file_name, "fileType": file_suffix}
        org_group_send_headers = dingtalkrobot__1__0_models.OrgGroupSendHeaders()
        org_group_send_headers.x_acs_dingtalk_access_token = access_token
        org_group_send_request = dingtalkrobot__1__0_models.OrgGroupSendRequest(
            msg_param=f'{param}',
            msg_key=self.FILE_TYPE,
            robot_code=robot_code,
            open_conversation_id=open_conversation_id
        )
        resp = self.__client1_0.org_group_send_with_options(org_group_send_request, org_group_send_headers,
                                                            util_models.RuntimeOptions())
        if resp.status_code != self.success_status_code:
            Log.file().error(f"文件发送失败！文件名：{file_name}")

    def upload_request(self, access_token, file_name, file_content, file_type):
        """
        往钉钉上传媒体文件，期间会将二进制文件临时写到本地，在上传完成后删掉。
        PS：此接口调用最好关闭VPN地理模式，以免通过代理服务器连接目标URL产生的异常
        :param access_token: 接口调用凭证
        :param file_name: 文件名称
        :param file_content: base64编码后的文件
        :param file_type: 文件类型 图像‘image’、音频‘voice’、视频‘video’、其他‘file’
        :return 媒体文件上传后获取的唯一标识
        """
        screenshot_dir = Util.unified_static_path(constants.TEMP_SAVE_DIRECTORY)
        screenshot_path = os.path.join(screenshot_dir, file_name)
        # 将二进制文件处理成满足钉钉需要的媒体文件标识
        # 先将二进制文件写到本地然后读取出来，完成上传后删掉
        decoded_content = base64.b64decode(file_content)
        with open(screenshot_path, 'wb') as file:
            file.write(decoded_content)
        while not os.path.exists(screenshot_path):
            pass
        file = open(screenshot_path, 'rb')
        url = f"{DingtalkUrl.MEDIA_UPLOAD_REQUEST}?access_token={access_token}"
        data = {
            'type': file_type
        }
        files = {
            'media': file
        }
        resp = requests.post(url, data=data, files=files)
        file.close()
        os.remove(screenshot_path)
        if resp.status_code != self.success_status_code:
            return ""
        rj = resp.json()
        if rj[self.err_code_key] != 0:
            Log.file().error(f"文件上传失败！{rj[self.err_msg_key]}")
            return ""
        r_data = json.loads(resp.text)
        return r_data.get("media_id")

    def get_access_token(self, context) -> str:
        """
        获取接口免登凭证，如果启用了缓存将优先从缓存中获取凭证，缓存未命中的情况下再调用钉钉接口重新获取。该机制
        是为了避免触发钉钉的频率限制
        """
        app_key = context.get(configuration.DING_APP_KEY_ATT)
        app_secret = context.get(configuration.DING_APP_SECRET_ATT)
        redis_sv = RedisUtils.RedisService() if configuration.ENABLE_CACHE else None
        if redis_sv:
            # 启用缓存则从缓存获取
            cache_access_token = redis_sv.get_ding_access_token(app_key, app_secret)
            Log.file().debug(f"缓存中的凭证：{cache_access_token}")
            if cache_access_token is not None:
                return cache_access_token
        get_access_token_request = dingtalkoauth_2__1__0_models.GetAccessTokenRequest(
            app_key=app_key,
            app_secret=app_secret
        )
        resp = self.__client2_1.get_access_token(get_access_token_request)
        if resp.status_code == self.success_status_code:
            access_token = resp.body.access_token
            Log.file().debug(f"钉钉凭证：{access_token}")
            if redis_sv:
                # 更新缓存
                expire_time = resp.body.expire_in
                Log.file().debug(f"更新凭证缓存，凭证有效时间{expire_time}")
                redis_sv.set_ding_access_token(app_key, app_secret, access_token, expire_time)
            return access_token
        return ""
